﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Attributes;
using FernUniHagen.CompilerExtension.Values;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;
using FernUniHagen.CompilerExtension.Adaptor;
//using Roslyn.Compilers.CSharp;

namespace FernUniHagen.CompilerExtension.Functions
{
    /// <summary>
    /// Utilities class contains all functions specific for multiplicities
    /// </summary>
    public class Utilities
    {
        //public static CommonSyntaxNode GetRoslynTypeSyntax(CommonSyntaxNode node)
        //{
        //    CommonSyntaxNode returnNode = null;
        //    FieldDeclarationSyntax annotatedField = node.FirstAncestorOrSelf<FieldDeclarationSyntax>();
        //    PropertyDeclarationSyntax annotatedProperty = node.FirstAncestorOrSelf<PropertyDeclarationSyntax>();
        //    ParameterSyntax annotatedParameter = node.FirstAncestorOrSelf<ParameterSyntax>();
        //    MethodDeclarationSyntax annotatedMethod = node.FirstAncestorOrSelf<MethodDeclarationSyntax>();
        //    ClassDeclarationSyntax aClass = node.FirstAncestorOrSelf<ClassDeclarationSyntax>();
        //    if (annotatedField != null)
        //    {
        //        returnNode = annotatedField;
        //    }
        //    else if (annotatedProperty != null)
        //    {
        //        returnNode = annotatedProperty;
        //    }
        //    else if (annotatedParameter != null)
        //    {
        //        returnNode = annotatedParameter;
        //    }
        //    else if (annotatedMethod != null)
        //    {
        //        returnNode = annotatedMethod;
        //    }
        //    else if (aClass != null)
        //    {
        //        returnNode = aClass;
        //    }
        //    return returnNode;
        //}

        /// <summary>
        /// TODO: Check if necessary
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="node"></param>
        /// <returns></returns>
        public static T CastNode<T>(CommonSyntaxNode node) where T : CommonSyntaxNode
        {
            if (!(node is T))
                throw new Exception("Cast not possible");

            return (T)node;
        }

        public static AttributeType GetAttributeType(ISemanticModel semanticModel, AttributeSyntax attribute)
        {
            AttributeType attrType = AttributeType.None;
            CommonTypeInfo typeInfo = semanticModel.GetTypeInfo(attribute);
            switch (typeInfo.Type.Name)
            {
                case Constants.AttributeName.Any:
                case Constants.AttributeName.AnyAttribute:
                    attrType = AttributeType.Any;
                    break;
                case Constants.AttributeName.Option:
                case Constants.AttributeName.OptionAttribute:
                    attrType = AttributeType.Option;
                    break;
                case Constants.AttributeName.Bare:
                case Constants.AttributeName.BareAttribute:
                    attrType = AttributeType.Bare;
                    break;
            }
            return attrType;
        }

        //public static CommonSyntaxNode FindDecleration(ISemanticModel model, IdentifierNameSyntax identifier)
        //{
        //    CommonSymbolInfo symbolInfo = model.GetSymbolInfo(identifier);
        //    ISymbol symbol = symbolInfo.Symbol;
        //    ISymbol originalDefinition = symbol.OriginalDefinition;

        //    //The code is somewhere else and we can't do anything
        //    if (symbol.Locations.FirstOrDefault().IsInMetadata) return null;
        //    //Bad but recommended here http://stackoverflow.com/questions/11428662/how-to-find-the-syntaxnode-for-a-method-symbol-in-a-compilationunit
        //    var position = symbol.Locations.First().SourceSpan.Start;

            

        //    //Check if this is needed or if we can just return the parent
        //    var node = GetRoslynTypeSyntax(symbol.Locations.First().SourceTree.GetRoot().FindToken(position).Parent);

        //    return node;
        //}

        ///// <summary>
        ///// TODO:Need to fix for all cases (not only for the first)
        ///// Taken from Harry but adjusted
        ///// </summary>
        ///// <param name="pSem"></param>
        ///// <param name="exprName"></param>
        ///// <returns></returns>
        //public static CommonSyntaxNode FindDecleration(ISemanticModel pSem, SimpleNameSyntax exprName)
        //{
        //    var declNode = pSem.GetSymbolInfo(exprName).Symbol.OriginalDefinition.DeclaringSyntaxNodes.First();
        //    if (declNode == null) //case it's a call to a member not defined in this solution
        //    {
        //        return null;
        //    }
        //    return declNode.GetDeclarationType();
        //}

        //public static AttributeType GetMultiplicityAttribute(CommonSyntaxNode node)
        //{
        //    AttributeType multyType = AttributeType.None;
        //    GenericNameSyntax genericName = null;

        //    GetMultiplicityAttribute(node, out multyType, out genericName);
        //    return multyType;
        //}

        ///// <summary>
        ///// Gets information about the attribute of the node
        ///// </summary>
        ///// <param name="CommonSyntaxNode">the node with the attribute</param>
        ///// <param name="multiplicityType">the type of multiplicity (any...)</param>
        ///// <param name="genericName">genericname syntax in case of any</param>
        ///// <returns>returns a list of errors found</returns>
        //public static List<string> GetMultiplicityAttribute(CommonSyntaxNode node, out AttributeType multiplicityType,
        //    out GenericNameSyntax genericName)
        //{
            
        //    List<string> errors = new List<string>();
        //    multiplicityType = AttributeType.None;
        //    genericName = null;
        //    var declaration = SyntaxElementFactory.GetDeclaration(node,null);
 
        //    if (declaration != null)
        //    {
        //        declaration.GetMultiplicityAttribute(out multiplicityType, out genericName);
        //    }
        //    return errors;
        //}

        /// <summary>
        /// Converts AttributeList to multiplicity type and genericNameSyntax if possible
        /// </summary>
        /// <param name="attributesList">List of attributes</param>
        /// <param name="multiplicityType">the type of multiplicity (any...)</param>
        /// <param name="genericName">genericname syntax in case of any</param>
        /// <returns>returns a list of errors found</returns>
        private static List<string> GetMultiplicityAttribute(SyntaxList<AttributeListSyntax> attributesList, TypeSyntax type, out AttributeType multiplicityType,
            out GenericNameSyntax genericName)
        {
            List<string> errors = new List<string>();
            genericName = null;
            multiplicityType = AttributeType.None;
            List<AttributeSyntax> attributesFound = new List<AttributeSyntax>();
            foreach (var attrListSyntax in attributesList)
            {
                foreach (var attribute in attrListSyntax.Attributes)
                {
                    switch (attribute.Name.ToString())
                    {
                        case Constants.AttributeName.Any:
                        case Constants.AttributeName.AnyAttribute:
                            multiplicityType = AttributeType.Any;
                            attributesFound.Add(attribute);
                            genericName = 
                                (GenericNameSyntax)(attribute.ArgumentList.Arguments.First().Expression as TypeOfExpressionSyntax).Type;
                            genericName = CreateGenericType(type, genericName);

                            break;
                        case Constants.AttributeName.Option:
                        case Constants.AttributeName.OptionAttribute:
                            multiplicityType = AttributeType.Option;
                            attributesFound.Add(attribute);
                            break;
                        case Constants.AttributeName.Bare:
                        
                        case Constants.AttributeName.BareAttribute:
                            multiplicityType = AttributeType.Bare;
                            attributesFound.Add(attribute);
                            break;
                           

                    }
                }
            }
            if (attributesFound.Count == 0)
            {
                errors.Add("No multiplicity attribute found on this item");
            }
            else if (attributesFound.Count > 1)
            {
                errors.Add("Too many multiplicity attribute found on this item");
            }
            return errors;
        }

        /// <summary>
        /// Converts the "type" into the generic type annotated  
        /// </summary>
        /// <param name="type">the type that is taken for the new generic type</param>
        /// <param name="annotatedType">The generic type with which the type is combined</param>
        /// <returns>a new Generic Type containing the type as typeparameter and the annotated type as generic type</returns>
        public static GenericNameSyntax CreateGenericType(TypeSyntax type, GenericNameSyntax annotatedType)
        {
            SeparatedSyntaxList<TypeSyntax> typeSyntaxes = new SeparatedSyntaxList<TypeSyntax>();
            typeSyntaxes = typeSyntaxes.Add(type);
            TypeArgumentListSyntax newTypeArguments = annotatedType.TypeArgumentList.WithArguments(typeSyntaxes);
            GenericNameSyntax newGenericType = Syntax.GenericName(annotatedType.Identifier, newTypeArguments);
            newGenericType = newGenericType.WithTrailingTrivia(Syntax.Space);
            return newGenericType;
        }

        public static GenericNameSyntax CreateGenericDictionaryType(TypeSyntax typeOfKey, TypeSyntax typeOfDict, GenericNameSyntax annotatedType)
        {
            SeparatedSyntaxList<TypeSyntax> typeSyntaxes = new SeparatedSyntaxList<TypeSyntax>();

            typeOfKey = typeOfKey.WithLeadingTrivia(Syntax.Space);
            typeOfKey = typeOfKey.WithTrailingTrivia(Syntax.Space);
            typeOfDict = typeOfDict.WithLeadingTrivia(Syntax.Space);
            typeOfDict = typeOfDict.WithTrailingTrivia(Syntax.Space);

            typeSyntaxes = typeSyntaxes.Add(typeOfKey);
            typeSyntaxes = typeSyntaxes.Add(typeOfDict);
            TypeArgumentListSyntax newTypeArguments = annotatedType.TypeArgumentList.WithArguments(typeSyntaxes);
            GenericNameSyntax newGenericType = Syntax.GenericName(annotatedType.Identifier, newTypeArguments);
            newGenericType = newGenericType.WithTrailingTrivia(Syntax.Space);
            return newGenericType;
        }

        /// <summary>
        /// TODO: Check if necessary
        /// Gets the type of a node (for now FieldDecleration, PropertyDecleration )
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        public static TypeSyntax GetType(CommonSyntaxNode node)
        {
            if (node is FieldDeclarationSyntax)
            {
                return ((FieldDeclarationSyntax)node).Declaration.Type;
            }
            else if (node is ParameterSyntax)
            {
                return ((ParameterSyntax)node).Type;
            }
            else if (node is PropertyDeclarationSyntax)
            {
                return ((PropertyDeclarationSyntax)node).Type;
            }else if(node is MethodDeclarationSyntax)
            {
                return ((MethodDeclarationSyntax)node).ReturnType;
            }
            return null;
        }

       


    }
}
